//----------------------------------------------
//            NGUI: Next-Gen UI kit
// Copyright © 2011-2014 Tasharen Entertainment
//----------------------------------------------

#if !UNITY_3_5
#define DYNAMIC_FONT
#endif

using UnityEngine;
using System.Text;
using System.Diagnostics;

/// <summary>
/// Helper class containing functionality related to using dynamic fonts.
/// </summary>

static public class NGUIText
{
    public enum Alignment
    {
        Automatic,
        Left,
        Center,
        Right,
        Justified,
    }

    public enum SymbolStyle
    {
        None,
        Normal,
        Colored,
    }

    public class GlyphInfo
    {
        public Vector2 v0;
        public Vector2 v1;
        public Vector2 u0;
        public Vector2 u1;
        public Vector2 u2;
        public Vector2 u3;
        public float advance = 0f;
        public int channel = 0;
    }

    /// <summary>
    /// When printing text, a lot of additional data must be passed in. In order to save allocations,
    /// this data is not passed at all, but is rather set in a single place before calling the functions that use it.
    /// </summary>

    static public UIFont bitmapFont;
#if DYNAMIC_FONT
    static public Font dynamicFont;
#endif
    static public GlyphInfo glyph = new GlyphInfo();

    static public int fontSize = 16;
    static public float fontScale = 1f;
    static public float pixelDensity = 1f;
    static public FontStyle fontStyle = FontStyle.Normal;
    static public Alignment alignment = Alignment.Left;
    static public Color tint = Color.white;

    static public int rectWidth = 1000000;
    static public int rectHeight = 1000000;
    static public int regionWidth = 1000000;
    static public int regionHeight = 1000000;
    static public int maxLines = 0;

    static public bool gradient = false;
    static public Color gradientBottom = Color.white;
    static public Color gradientTop = Color.white;

    static public bool encoding = false;
    static public float spacingX = 0f;
    static public float spacingY = 0f;
    static public bool premultiply = false;
    static public SymbolStyle symbolStyle;

    static public int finalSize = 0;
    static public float finalSpacingX = 0f;
    static public float finalLineHeight = 0f;
    static public float baseline = 0f;
    static public bool useSymbols = false;

    /// <summary>
    /// Recalculate the 'final' values.
    /// </summary>

    static public void Update() { Update(true); }

    /// <summary>
    /// Recalculate the 'final' values.
    /// </summary>

    static public void Update(bool request)
    {
        finalSize = Mathf.RoundToInt(fontSize / pixelDensity);
        finalSpacingX = spacingX * fontScale;
        finalLineHeight = (fontSize + spacingY) * fontScale;
        useSymbols = (bitmapFont != null && bitmapFont.hasSymbols) && encoding && symbolStyle != SymbolStyle.None;

#if DYNAMIC_FONT
        if (dynamicFont != null && request)
        {
            dynamicFont.RequestCharactersInTexture(")_-", finalSize, fontStyle);

#if UNITY_4_3 || UNITY_4_5 || UNITY_4_6
			if (!dynamicFont.GetCharacterInfo(')', out mTempChar, finalSize, fontStyle) || mTempChar.vert.height == 0f)
			{
				dynamicFont.RequestCharactersInTexture("A", finalSize, fontStyle);
				{
					if (!dynamicFont.GetCharacterInfo('A', out mTempChar, finalSize, fontStyle))
					{
						baseline = 0f;
						return;
					}
				}
			}

			float y0 = mTempChar.vert.yMax;
			float y1 = mTempChar.vert.yMin;
#else
            if (!dynamicFont.GetCharacterInfo(')', out mTempChar, finalSize, fontStyle) || mTempChar.maxY == 0f)
            {
                dynamicFont.RequestCharactersInTexture("A", finalSize, fontStyle);
                {
                    if (!dynamicFont.GetCharacterInfo('A', out mTempChar, finalSize, fontStyle))
                    {
                        baseline = 0f;
                        return;
                    }
                }
            }

            float y0 = mTempChar.maxY;
            float y1 = mTempChar.minY;
#endif
            baseline = Mathf.Round(y0 + (finalSize - y0 + y1) * 0.5f);
        }
#endif
    }

    /// <summary>
    /// Prepare to use the specified text.
    /// </summary>

    static public void Prepare(string text)
    {
#if DYNAMIC_FONT
        if (dynamicFont != null)
            dynamicFont.RequestCharactersInTexture(text, finalSize, fontStyle);
#endif
    }

    /// <summary>
    /// Get the specified symbol.
    /// </summary>

    static public BMSymbol GetSymbol(string text, int index, int textLength)
    {
        return (bitmapFont != null) ? bitmapFont.MatchSymbol(text, index, textLength) : null;
    }

    /// <summary>
    /// Get the width of the specified glyph. Returns zero if the glyph could not be retrieved.
    /// </summary>

    static public float GetGlyphWidth(int ch, int prev, int Size = 0)
    {
        if (bitmapFont != null)
        {
            bool thinSpace = false;

            if (ch == '\u2009')
            {
                thinSpace = true;
                ch = ' ';
            }

            BMGlyph bmg = bitmapFont.bmFont.GetGlyph(ch);

            if (bmg != null)
            {
                int adv = bmg.advance;
                if (thinSpace) adv >>= 1;
                return fontScale * ((prev != 0) ? adv + bmg.GetKerning(prev) : bmg.advance);
            }
        }
        else if (Size != 0)
        {
            if (dynamicFont.GetCharacterInfo((char)ch, out mTempChar, Size, fontStyle))
            {
                return mTempChar.advance * fontScale * pixelDensity;
            }
        }
#if DYNAMIC_FONT
        else if (dynamicFont != null)
        {
            if (dynamicFont.GetCharacterInfo((char)ch, out mTempChar, finalSize, fontStyle))
#if UNITY_4_3 || UNITY_4_5 || UNITY_4_6
				return mTempChar.width * fontScale * pixelDensity;
#else
                return mTempChar.advance * fontScale * pixelDensity;
#endif
        }
#endif

        //#if DYNAMIC_FONT
        //        if (Size != 0)
        //        {

        //            if (dynamicFont != null)
        //            {
        //                if (dynamicFont.GetCharacterInfo((char)ch, out mTempChar, Size, fontStyle))
        //#if UNITY_4_3 || UNITY_4_5 || UNITY_4_6
        //				return mTempChar.width * fontScale * pixelDensity;
        //#else
        //                    return mTempChar.advance * fontScale * pixelDensity;
        //#endif
        //            }
        //        }
        return 0f;
    }

    /// <summary>
    /// Get the specified glyph.
    /// </summary>

    static public GlyphInfo GetGlyph(int ch, int prev)
    {
        if (bitmapFont != null)
        {
            bool thinSpace = false;

            if (ch == '\u2009')
            {
                thinSpace = true;
                ch = ' ';
            }

            BMGlyph bmg = bitmapFont.bmFont.GetGlyph(ch);

            if (bmg != null)
            {
                int kern = (prev != 0) ? bmg.GetKerning(prev) : 0;
                glyph.v0.x = (prev != 0) ? bmg.offsetX + kern : bmg.offsetX;
                glyph.v1.y = -bmg.offsetY;

                glyph.v1.x = glyph.v0.x + bmg.width;
                glyph.v0.y = glyph.v1.y - bmg.height;

                glyph.u0.x = bmg.x;
                glyph.u0.y = bmg.y + bmg.height;

                glyph.u2.x = bmg.x + bmg.width;
                glyph.u2.y = bmg.y;

                glyph.u1.x = glyph.u0.x;
                glyph.u1.y = glyph.u2.y;

                glyph.u3.x = glyph.u2.x;
                glyph.u3.y = glyph.u0.y;

                int adv = bmg.advance;
                if (thinSpace) adv >>= 1;
                glyph.advance = adv + kern;
                glyph.channel = bmg.channel;

                if (fontScale != 1f)
                {
                    glyph.v0 *= fontScale;
                    glyph.v1 *= fontScale;
                    glyph.advance *= fontScale;
                }
                return glyph;
            }
        }
#if DYNAMIC_FONT
        else if (dynamicFont != null)
        {
            if (dynamicFont.GetCharacterInfo((char)ch, out mTempChar, finalSize, fontStyle))
            {
#if UNITY_4_3 || UNITY_4_5 || UNITY_4_6
				glyph.v0.x = mTempChar.vert.xMin;
				glyph.v1.x = glyph.v0.x + mTempChar.vert.width;

				glyph.v0.y = mTempChar.vert.yMax - baseline;
				glyph.v1.y = glyph.v0.y - mTempChar.vert.height;

				glyph.u0.x = mTempChar.uv.xMin;
				glyph.u0.y = mTempChar.uv.yMin;

				glyph.u2.x = mTempChar.uv.xMax;
				glyph.u2.y = mTempChar.uv.yMax;

				if (mTempChar.flipped)
				{
					glyph.u1 = new Vector2(glyph.u2.x, glyph.u0.y);
					glyph.u3 = new Vector2(glyph.u0.x, glyph.u2.y);
				}
				else
				{
					glyph.u1 = new Vector2(glyph.u0.x, glyph.u2.y);
					glyph.u3 = new Vector2(glyph.u2.x, glyph.u0.y);
				}

				glyph.advance = mTempChar.width;
				glyph.channel = 0;
#else
                glyph.v0.x = mTempChar.minX;
                glyph.v1.x = mTempChar.maxX;

                glyph.v0.y = mTempChar.maxY - baseline;
                glyph.v1.y = mTempChar.minY - baseline;

                glyph.u0 = mTempChar.uvTopLeft;
                glyph.u1 = mTempChar.uvBottomLeft;
                glyph.u2 = mTempChar.uvBottomRight;
                glyph.u3 = mTempChar.uvTopRight;

                glyph.advance = mTempChar.advance;
                glyph.channel = 0;
#endif
                glyph.v0.x = Mathf.Round(glyph.v0.x);
                glyph.v0.y = Mathf.Round(glyph.v0.y);
                glyph.v1.x = Mathf.Round(glyph.v1.x);
                glyph.v1.y = Mathf.Round(glyph.v1.y);

                float pd = fontScale * pixelDensity;

                if (pd != 1f)
                {
                    glyph.v0 *= pd;
                    glyph.v1 *= pd;
                    glyph.advance *= pd;
                }
                return glyph;
            }
        }
#endif
        return null;
    }

    static Color mInvisible = new Color(0f, 0f, 0f, 0f);
    static BetterList<Color> mColors = new BetterList<Color>();
    static float mAlpha = 1f;
#if DYNAMIC_FONT
    static CharacterInfo mTempChar;
#endif

    /// <summary>
    /// Parse Aa syntax alpha encoded in the string.
    /// </summary>

    [System.Diagnostics.DebuggerHidden]
    [System.Diagnostics.DebuggerStepThrough]
    static public float ParseAlpha(string text, int index)
    {
        int a = (NGUIMath.HexToDecimal(text[index + 1]) << 4) | NGUIMath.HexToDecimal(text[index + 2]);
        return Mathf.Clamp01(a / 255f);
    }

    /// <summary>
    /// Parse a RrGgBb color encoded in the string.
    /// </summary>

    [System.Diagnostics.DebuggerHidden]
    [System.Diagnostics.DebuggerStepThrough]
    static public Color ParseColor(string text, int offset) { return ParseColor24(text, offset); }

    /// <summary>
    /// Parse a RrGgBb color encoded in the string.
    /// </summary>

    [System.Diagnostics.DebuggerHidden]
    [System.Diagnostics.DebuggerStepThrough]
    static public Color ParseColor24(string text, int offset)
    {
        int r = (NGUIMath.HexToDecimal(text[offset]) << 4) | NGUIMath.HexToDecimal(text[offset + 1]);
        int g = (NGUIMath.HexToDecimal(text[offset + 2]) << 4) | NGUIMath.HexToDecimal(text[offset + 3]);
        int b = (NGUIMath.HexToDecimal(text[offset + 4]) << 4) | NGUIMath.HexToDecimal(text[offset + 5]);
        float f = 1f / 255f;
        return new Color(f * r, f * g, f * b);
    }

    /// <summary>
    /// Parse a RrGgBbAa color encoded in the string.
    /// </summary>

    [System.Diagnostics.DebuggerHidden]
    [System.Diagnostics.DebuggerStepThrough]
    static public Color ParseColor32(string text, int offset)
    {
        int r = (NGUIMath.HexToDecimal(text[offset]) << 4) | NGUIMath.HexToDecimal(text[offset + 1]);
        int g = (NGUIMath.HexToDecimal(text[offset + 2]) << 4) | NGUIMath.HexToDecimal(text[offset + 3]);
        int b = (NGUIMath.HexToDecimal(text[offset + 4]) << 4) | NGUIMath.HexToDecimal(text[offset + 5]);
        int a = (NGUIMath.HexToDecimal(text[offset + 6]) << 4) | NGUIMath.HexToDecimal(text[offset + 7]);
        float f = 1f / 255f;
        return new Color(f * r, f * g, f * b, f * a);
    }

    /// <summary>
    /// The reverse of ParseColor -- encodes a color in RrGgBb format.
    /// </summary>

    [System.Diagnostics.DebuggerHidden]
    [System.Diagnostics.DebuggerStepThrough]
    static public string EncodeColor(Color c) { return EncodeColor24(c); }

    /// <summary>
    /// The reverse of ParseAlpha -- encodes a color in Aa format.
    /// </summary>

    [System.Diagnostics.DebuggerHidden]
    [System.Diagnostics.DebuggerStepThrough]
    static public string EncodeAlpha(float a)
    {
        int i = Mathf.Clamp(Mathf.RoundToInt(a * 255f), 0, 255);
        return NGUIMath.DecimalToHex8(i);
    }

    /// <summary>
    /// The reverse of ParseColor24 -- encodes a color in RrGgBb format.
    /// </summary>

    [System.Diagnostics.DebuggerHidden]
    [System.Diagnostics.DebuggerStepThrough]
    static public string EncodeColor24(Color c)
    {
        int i = 0xFFFFFF & (NGUIMath.ColorToInt(c) >> 8);
        return NGUIMath.DecimalToHex24(i);
    }

    /// <summary>
    /// The reverse of ParseColor32 -- encodes a color in RrGgBb format.
    /// </summary>

    [System.Diagnostics.DebuggerHidden]
    [System.Diagnostics.DebuggerStepThrough]
    static public string EncodeColor32(Color c)
    {
        int i = NGUIMath.ColorToInt(c);
        return NGUIMath.DecimalToHex32(i);
    }

    /// <summary>
    /// Parse an embedded symbol, such as [FFAA00] (set color) or [-] (undo color change). Returns whether the index was adjusted.
    /// </summary>

    static public bool ParseSymbol(string text, ref int index)
    {
        int n = 1;
        bool bold = false;
        bool italic = false;
        bool underline = false;
        bool strikethrough = false;
        bool ignoreColor = false;
        return ParseSymbol(text, ref index, null, false, ref n, ref bold, ref italic, ref underline, ref strikethrough, ref ignoreColor);
    }

    /// <summary>
    /// Whether the specified character falls under the 'hex' character category (0-9, A-F).
    /// </summary>

    [System.Diagnostics.DebuggerHidden]
    [System.Diagnostics.DebuggerStepThrough]
    static public bool IsHex(char ch)
    {
        return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
    }

    /// <summary>
    /// Parse the symbol, if possible. Returns 'true' if the 'index' was adjusted.
    /// Advanced symbol support originally contributed by Rudy Pangestu.
    /// </summary>

    static public bool ParseSymbol(string text, ref int index, BetterList<Color> colors, bool premultiply,
        ref int sub, ref bool bold, ref bool italic, ref bool underline, ref bool strike, ref bool ignoreColor)
    {
        int length = text.Length;

        if (index + 3 > length || text[index] != '[') return false;

        if (text[index + 2] == ']')
        {
            if (text[index + 1] == '-')
            {
                if (colors != null && colors.size > 1)
                    colors.RemoveAt(colors.size - 1);
                index += 3;
                return true;
            }

            string sub3 = text.Substring(index, 3);

            switch (sub3)
            {
                case "[b]":
                    bold = true;
                    index += 3;
                    return true;

                case "[i]":
                    italic = true;
                    index += 3;
                    return true;

                case "[u]":
                    underline = true;
                    index += 3;
                    return true;

                case "[s]":
                    strike = true;
                    index += 3;
                    return true;

                case "[c]":
                    ignoreColor = true;
                    index += 3;
                    return true;
            }
        }

        if (index + 4 > length) return false;

        if (text[index + 3] == ']')
        {
            string sub4 = text.Substring(index, 4);

            switch (sub4)
            {
                case "[/b]":
                    bold = false;
                    index += 4;
                    return true;

                case "[/i]":
                    italic = false;
                    index += 4;
                    return true;

                case "[/u]":
                    underline = false;
                    index += 4;
                    return true;

                case "[/s]":
                    strike = false;
                    index += 4;
                    return true;

                case "[/c]":
                    ignoreColor = false;
                    index += 4;
                    return true;

                default:
                    {
                        char ch0 = text[index + 1];
                        char ch1 = text[index + 2];

                        if (IsHex(ch0) && IsHex(ch1))
                        {
                            int a = (NGUIMath.HexToDecimal(ch0) << 4) | NGUIMath.HexToDecimal(ch1);
                            mAlpha = a / 255f;
                            index += 4;
                            return true;
                        }
                    }
                    break;
            }
        }

        if (index + 5 > length) return false;

        if (text[index + 4] == ']')
        {
            string sub5 = text.Substring(index, 5);

            switch (sub5)
            {
                case "[sub]":
                    sub = 1;
                    index += 5;
                    return true;

                case "[sup]":
                    sub = 2;
                    index += 5;
                    return true;
            }
        }

        if (index + 6 > length) return false;

        if (text[index + 5] == ']')
        {
            string sub6 = text.Substring(index, 6);

            switch (sub6)
            {
                case "[/sub]":
                    sub = 0;
                    index += 6;
                    return true;

                case "[/sup]":
                    sub = 0;
                    index += 6;
                    return true;

                case "[/url]":
                    index += 6;
                    return true;
            }
        }

        if (text[index + 1] == 'u' && text[index + 2] == 'r' && text[index + 3] == 'l' && text[index + 4] == '=')
        {
            int closingBracket = text.IndexOf(']', index + 4);

            if (closingBracket != -1)
            {
                index = closingBracket + 1;
                return true;
            }
            else
            {
                index = text.Length;
                return true;
            }
        }

        if (index + 8 > length) return false;

        if (text[index + 7] == ']')
        {
            Color c = ParseColor24(text, index + 1);

            if (EncodeColor24(c) != text.Substring(index + 1, 6).ToUpper())
                return false;

            if (colors != null)
            {
                c.a = colors[colors.size - 1].a;
                if (premultiply && c.a != 1f)
                    c = Color.Lerp(mInvisible, c, c.a);
                colors.Add(c);
            }
            index += 8;
            return true;
        }

        if (index + 10 > length) return false;

        if (text[index + 9] == ']')
        {
            Color c = ParseColor32(text, index + 1);
            if (EncodeColor32(c) != text.Substring(index + 1, 8).ToUpper())
                return false;

            if (colors != null)
            {
                if (premultiply && c.a != 1f)
                    c = Color.Lerp(mInvisible, c, c.a);
                colors.Add(c);
            }
            index += 10;
            return true;
        }
        return false;
    }

    /// <summary>
    /// Runs through the specified string and removes all color-encoding symbols.
    /// </summary>

    static public string StripSymbols(string text)
    {
        if (text != null)
        {
            for (int i = 0, imax = text.Length; i < imax;)
            {
                char c = text[i];

                if (c == '[')
                {
                    int sub = 0;
                    bool bold = false;
                    bool italic = false;
                    bool underline = false;
                    bool strikethrough = false;
                    bool ignoreColor = false;
                    int retVal = i;

                    if (ParseSymbol(text, ref retVal, null, false, ref sub, ref bold, ref italic, ref underline, ref strikethrough, ref ignoreColor))
                    {
                        text = text.Remove(i, retVal - i);
                        imax = text.Length;
                        continue;
                    }
                }
                ++i;
            }
        }
        return text;
    }

    /// <summary>
    /// Align the vertices to be right or center-aligned given the line width specified by NGUIText.lineWidth.
    /// </summary>

    static public void Align(BetterList<Vector3> verts, int indexOffset, float printedWidth)
    {
        switch (alignment)
        {
            case Alignment.Right:
                {
                    float padding = rectWidth - printedWidth;
                    if (padding < 0f) return;
#if UNITY_FLASH
				for (int i = indexOffset; i < verts.size; ++i)
					verts.buffer[i] = verts.buffer[i] + new Vector3(padding, 0f);
#else
                    for (int i = indexOffset; i < verts.size; ++i)
                        verts.buffer[i].x += padding;
#endif
                    break;
                }

            case Alignment.Center:
                {
                    float padding = (rectWidth - printedWidth) * 0.5f;
                    if (padding < 0f) return;

                    // Keep it pixel-perfect
                    int diff = Mathf.RoundToInt(rectWidth - printedWidth);
                    int intWidth = Mathf.RoundToInt(rectWidth);

                    bool oddDiff = (diff & 1) == 1;
                    bool oddWidth = (intWidth & 1) == 1;
                    if ((oddDiff && !oddWidth) || (!oddDiff && oddWidth))
                        padding += 0.5f * fontScale;
#if UNITY_FLASH
				for (int i = indexOffset; i < verts.size; ++i)
					verts.buffer[i] = verts.buffer[i] + new Vector3(padding, 0f);
#else
                    for (int i = indexOffset; i < verts.size; ++i)
                        verts.buffer[i].x += padding;
#endif
                    break;
                }

            case Alignment.Justified:
                {
                    // Printed text needs to reach at least 65% of the width in order to be justified
                    if (printedWidth < rectWidth * 0.65f) return;

                    // There must be some padding involved
                    float padding = (rectWidth - printedWidth) * 0.5f;
                    if (padding < 1f) return;

                    // There must be at least two characters
                    int chars = (verts.size - indexOffset) / 4;
                    if (chars < 1) return;

                    float progressPerChar = 1f / (chars - 1);
                    float scale = rectWidth / printedWidth;

                    for (int i = indexOffset + 4, charIndex = 1; i < verts.size; ++charIndex)
                    {
                        float x0 = verts.buffer[i].x;
                        float x1 = verts.buffer[i + 2].x;
                        float w = x1 - x0;
                        float x0a = x0 * scale;
                        float x1a = x0a + w;
                        float x1b = x1 * scale;
                        float x0b = x1b - w;
                        float progress = charIndex * progressPerChar;

                        x0 = Mathf.Lerp(x0a, x0b, progress);
                        x1 = Mathf.Lerp(x1a, x1b, progress);
                        x0 = Mathf.Round(x0);
                        x1 = Mathf.Round(x1);
#if UNITY_FLASH
					verts.buffer[i] = verts.buffer[i] + new Vector3(x0, 0f);
					verts.buffer[i+1] = verts.buffer[i+1] + new Vector3(x0, 0f);
					verts.buffer[i+2] = verts.buffer[i+2] + new Vector3(x1, 0f);
					verts.buffer[i+3] = verts.buffer[i+3] + new Vector3(x1, 0f);
					i += 4;
#else
                        verts.buffer[i++].x = x0;
                        verts.buffer[i++].x = x0;
                        verts.buffer[i++].x = x1;
                        verts.buffer[i++].x = x1;
#endif
                    }
                    break;
                }
        }
    }

    /// <summary>
    /// Get the index of the closest character within the provided list of values.
    /// Meant to be used with the arrays created by PrintExactCharacterPositions().
    /// </summary>

    static public int GetExactCharacterIndex(BetterList<Vector3> verts, BetterList<int> indices, Vector2 pos)
    {
        for (int i = 0; i < indices.size; ++i)
        {
            int i0 = (i << 1);
            int i1 = i0 + 1;

            float x0 = verts[i0].x;
            if (pos.x < x0) continue;

            float x1 = verts[i1].x;
            if (pos.x > x1) continue;

            float y0 = verts[i0].y;
            if (pos.y < y0) continue;

            float y1 = verts[i1].y;
            if (pos.y > y1) continue;

            return indices[i];
        }
        return 0;
    }

    /// <summary>
    /// Get the index of the closest vertex within the provided list of values.
    /// This function first sorts by Y, and only then by X.
    /// Meant to be used with the arrays created by PrintApproximateCharacterPositions().
    /// </summary>

    static public int GetApproximateCharacterIndex(BetterList<Vector3> verts, BetterList<int> indices, Vector2 pos)
    {
        // First sort by Y, and only then by X
        float bestX = float.MaxValue;
        float bestY = float.MaxValue;
        int bestIndex = 0;

        for (int i = 0; i < verts.size; ++i)
        {
            float diffY = Mathf.Abs(pos.y - verts[i].y);
            if (diffY > bestY) continue;

            float diffX = Mathf.Abs(pos.x - verts[i].x);

            if (diffY < bestY)
            {
                bestY = diffY;
                bestX = diffX;
                bestIndex = i;
            }
            else if (diffX < bestX)
            {
                bestX = diffX;
                bestIndex = i;
            }
        }
        return indices[bestIndex];
    }

    /// <summary>
    /// Whether the specified character is a space.
    /// </summary>

    [DebuggerHidden]
    [DebuggerStepThrough]
    static bool IsSpace(int ch) { return (ch == ' ' || ch == 0x200a || ch == 0x200b || ch == '\u2009'); }

    /// <summary>
    /// Convenience function that ends the line by either appending a new line character or replacing a space with one.
    /// </summary>

    [DebuggerHidden]
    [DebuggerStepThrough]
    static public void EndLine(ref StringBuilder s)
    {
        int i = s.Length - 1;
        if (i > 0 && IsSpace(s[i])) s[i] = '\n';
        else s.Append('\n');
    }

    /// <summary>
    /// Convenience function that ends the line by replacing a space with a newline character.
    /// </summary>

    [DebuggerHidden]
    [DebuggerStepThrough]
    static void ReplaceSpaceWithNewline(ref StringBuilder s)
    {
        //int i = s.Length - 1;
        //if (i > 0 && IsSpace(s[i])) s[i] = '\n';
    }

    /// <summary>
    /// Get the printed size of the specified string. The returned value is in pixels.
    /// </summary>

    static public Vector2 CalculatePrintedSize(string text)
    {
        Vector2 v = Vector2.zero;

        if (!string.IsNullOrEmpty(text))
        {
            // When calculating printed size, get rid of all symbols first since they are invisible anyway
            if (encoding) text = StripSymbols(text);

            // Ensure we have characters to work with
            Prepare(text);

            float x = 0f, y = 0f, maxX = 0f;
            int textLength = text.Length, ch = 0, prev = 0;

            for (int i = 0; i < textLength; ++i)
            {
                ch = text[i];

                // Start a new line
                if (ch == '\n')
                {
                    if (x > maxX) maxX = x;
                    x = 0f;
                    y += finalLineHeight;
                    continue;
                }

                // Skip invalid characters
                if (ch < ' ') continue;

                // See if there is a symbol matching this text
                BMSymbol symbol = useSymbols ? GetSymbol(text, i, textLength) : null;

                if (symbol == null)
                {
                    float w = GetGlyphWidth(ch, prev);

                    if (w != 0f)
                    {
                        w += finalSpacingX;

                        if (Mathf.RoundToInt(x + w) > regionWidth)
                        {
                            if (x > maxX) maxX = x - finalSpacingX;
                            x = w;
                            y += finalLineHeight;
                        }
                        else x += w;

                        prev = ch;
                    }
                }
                else
                {
                    float w = finalSpacingX + symbol.advance * fontScale;

                    if (Mathf.RoundToInt(x + w) > regionWidth)
                    {
                        if (x > maxX) maxX = x - finalSpacingX;
                        x = w;
                        y += finalLineHeight;
                    }
                    else x += w;

                    i += symbol.sequence.Length - 1;
                    prev = 0;
                }
            }

            v.x = ((x > maxX) ? x - finalSpacingX : maxX);
            v.y = (y + finalLineHeight);
        }
        return v;
    }

    static BetterList<float> mSizes = new BetterList<float>();

    /// <summary>
    /// Calculate the character index offset required to print the end of the specified text.
    /// </summary>

    static public int CalculateOffsetToFit(string text)
    {
        if (string.IsNullOrEmpty(text) || regionWidth < 1) return 0;

        Prepare(text);

        int textLength = text.Length, ch = 0, prev = 0;

        for (int i = 0, imax = text.Length; i < imax; ++i)
        {
            // See if there is a symbol matching this text
            BMSymbol symbol = useSymbols ? GetSymbol(text, i, textLength) : null;

            if (symbol == null)
            {
                ch = text[i];
                float w = GetGlyphWidth(ch, prev);
                if (w != 0f) mSizes.Add(finalSpacingX + w);
                prev = ch;
            }
            else
            {
                mSizes.Add(finalSpacingX + symbol.advance * fontScale);
                for (int b = 0, bmax = symbol.sequence.Length - 1; b < bmax; ++b) mSizes.Add(0);
                i += symbol.sequence.Length - 1;
                prev = 0;
            }
        }

        float remainingWidth = regionWidth;
        int currentCharacterIndex = mSizes.size;

        while (currentCharacterIndex > 0 && remainingWidth > 0)
            remainingWidth -= mSizes[--currentCharacterIndex];

        mSizes.Clear();

        if (remainingWidth < 0) ++currentCharacterIndex;
        return currentCharacterIndex;
    }

    /// <summary>
    /// Get the end of line that would fit into a field of given width.
    /// </summary>

    static public string GetEndOfLineThatFits(string text)
    {
        int textLength = text.Length;
        int offset = CalculateOffsetToFit(text);
        return text.Substring(offset, textLength - offset);
    }

    /// <summary>
    /// Text wrapping functionality. The 'width' and 'height' should be in pixels.
    /// </summary>

    static public bool WrapText(string text, out string finalText)
    {
        return WrapText(text, out finalText, false);
    }

    /// <summary>
    /// Text wrapping functionality. The 'width' and 'height' should be in pixels.
    /// </summary>

    static public bool WrapText(string text, out string finalText, bool keepCharCount)
    {
        if (regionWidth < 1 || regionHeight < 1 || finalLineHeight < 1f)
        {
            finalText = "";
            return false;
        }

        float height = (maxLines > 0) ? Mathf.Min(regionHeight, finalLineHeight * maxLines) : regionHeight;
        int maxLineCount = (maxLines > 0) ? maxLines : 1000000;
        maxLineCount = Mathf.FloorToInt(Mathf.Min(maxLineCount, height / finalLineHeight) + 0.01f);

        if (maxLineCount == 0)
        {
            finalText = "";
            return false;
        }

        if (string.IsNullOrEmpty(text)) text = " ";
        Prepare(text);

        StringBuilder sb = new StringBuilder();
        int textLength = text.Length;
        float remainingWidth = regionWidth;
        int start = 0, offset = 0, lineCount = 1, prev = 0;
        bool lineIsEmpty = true;
        bool fits = true;
        bool eastern = false;

        // Run through all characters
        for (; offset < textLength; ++offset)
        {
            char ch = text[offset];
            if (ch > 12287) eastern = true;

            // New line character -- start a new line
            if (ch == '\n')
            {
                if (lineCount == maxLineCount) break;
                remainingWidth = regionWidth;

                // Add the previous word to the final string
                if (start < offset) sb.Append(text.Substring(start, offset - start + 1));
                else sb.Append(ch);

                lineIsEmpty = true;
                ++lineCount;
                start = offset + 1;
                prev = 0;
                continue;
            }

            // When encoded symbols such as [RrGgBb] or [-] are encountered, skip past them
            if (encoding && ParseSymbol(text, ref offset)) { --offset; continue; }

            // See if there is a symbol matching this text
            BMSymbol symbol = useSymbols ? GetSymbol(text, offset, textLength) : null;

            // Calculate how wide this symbol or character is going to be
            float glyphWidth;

            if (symbol == null)
            {
                // Find the glyph for this character
                float w = GetGlyphWidth(ch, prev);
                if (w == 0f) continue;
                glyphWidth = finalSpacingX + w;
            }
            else glyphWidth = finalSpacingX + symbol.advance * fontScale;

            // Reduce the width
            remainingWidth -= glyphWidth;

            // If this marks the end of a word, add it to the final string.
            if (IsSpace(ch) && !eastern && start < offset)
            {
                int end = offset - start + 1;

                // Last word on the last line should not include an invisible character
                if (lineCount == maxLineCount && remainingWidth <= 0f && offset < textLength)
                {
                    char cho = text[offset];
                    if (cho < ' ' || IsSpace(cho)) --end;
                }

                sb.Append(text.Substring(start, end));
                lineIsEmpty = false;
                start = offset + 1;
                prev = ch;
            }

            // Doesn't fit?
            if (Mathf.RoundToInt(remainingWidth) < 0)
            {
                // Can't start a new line
                if (lineIsEmpty || lineCount == maxLineCount)
                {
                    // This is the first word on the line -- add it up to the character that fits
                    sb.Append(text.Substring(start, Mathf.Max(0, offset - start)));
                    bool space = IsSpace(ch);
                    if (!space && !eastern) fits = false;

                    if (lineCount++ == maxLineCount)
                    {
                        start = offset;
                        break;
                    }

                    if (keepCharCount) ReplaceSpaceWithNewline(ref sb);
                    else EndLine(ref sb);

                    // Start a brand-new line
                    lineIsEmpty = true;

                    if (space)
                    {
                        start = offset + 1;
                        remainingWidth = regionWidth;
                    }
                    else
                    {
                        start = offset;
                        remainingWidth = regionWidth - glyphWidth;
                    }
                    prev = 0;
                }
                else
                {
                    // Revert the position to the beginning of the word and reset the line
                    lineIsEmpty = true;
                    remainingWidth = regionWidth;
                    offset = start - 1;
                    prev = 0;

                    if (lineCount++ == maxLineCount) break;
                    if (keepCharCount) ReplaceSpaceWithNewline(ref sb);
                    else EndLine(ref sb);
                    continue;
                }
            }
            else prev = ch;

            // Advance the offset past the symbol
            if (symbol != null)
            {
                offset += symbol.length - 1;
                prev = 0;
            }
        }

        if (start < offset) sb.Append(text.Substring(start, offset - start));
        finalText = sb.ToString();
        return fits && ((offset == textLength) || (lineCount <= Mathf.Min(maxLines, maxLineCount)));
    }

    static Color32 s_c0, s_c1;

    /// <summary>
    /// Print the specified text into the buffers.
    /// </summary>

    static public void Print(string text, BetterList<Vector3> verts, BetterList<Vector2> uvs, BetterList<Color32> cols)
    {
        if (string.IsNullOrEmpty(text)) return;

        int indexOffset = verts.size;
        Prepare(text);

        // Start with the white tint
        mColors.Add(Color.white);
        mAlpha = 1f;

        int ch = 0, prev = 0;
        float x = 0f, y = 0f, maxX = 0f;
        float sizeF = finalSize;

        Color gb = tint * gradientBottom;
        Color gt = tint * gradientTop;
        Color32 uc = tint;
        int textLength = text.Length;

        Rect uvRect = new Rect();
        float invX = 0f, invY = 0f;
        float sizePD = sizeF * pixelDensity;

        // Advanced symbol support contributed by Rudy Pangestu.
        bool subscript = false;
        int subscriptMode = 0;  // 0 = normal, 1 = subscript, 2 = superscript
        bool bold = false;
        bool italic = false;
        bool underline = false;
        bool strikethrough = false;
        bool ignoreColor = false;
        const float sizeShrinkage = 0.75f;

        float v0x;
        float v1x;
        float v1y;
        float v0y;
        float prevX = 0;

        if (bitmapFont != null)
        {
            uvRect = bitmapFont.uvRect;
            invX = uvRect.width / bitmapFont.texWidth;
            invY = uvRect.height / bitmapFont.texHeight;
        }

        for (int i = 0; i < textLength; ++i)
        {
            ch = text[i];

            prevX = x;

            // New line character -- skip to the next line
            if (ch == '\n')
            {
                if (x > maxX) maxX = x;

                if (alignment != Alignment.Left)
                {
                    Align(verts, indexOffset, x - finalSpacingX);
                    indexOffset = verts.size;
                }

                x = 0;
                y += finalLineHeight;
                prev = 0;
                continue;
            }

            // Invalid character -- skip it
            if (ch < ' ')
            {
                prev = ch;
                continue;
            }

            // Color changing symbol
            if (encoding && ParseSymbol(text, ref i, mColors, premultiply, ref subscriptMode, ref bold,
                ref italic, ref underline, ref strikethrough, ref ignoreColor))
            {
                Color fc;

                if (ignoreColor)
                {
                    fc = mColors[mColors.size - 1];
                    fc.a *= mAlpha * tint.a;
                }
                else
                {
                    fc = tint * mColors[mColors.size - 1];
                    fc.a *= mAlpha;
                }
                uc = fc;

                for (int b = 0, bmax = mColors.size - 2; b < bmax; ++b)
                    fc.a *= mColors[b].a;

                if (gradient)
                {
                    gb = gradientBottom * fc;
                    gt = gradientTop * fc;
                }
                --i;
                continue;
            }

            // See if there is a symbol matching this text
            BMSymbol symbol = useSymbols ? GetSymbol(text, i, textLength) : null;

            if (symbol != null)
            {
                v0x = x + symbol.offsetX * fontScale;
                v1x = v0x + symbol.width * fontScale;
                v1y = -(y + symbol.offsetY * fontScale);
                v0y = v1y - symbol.height * fontScale;

                // Doesn't fit? Move down to the next line
                if (Mathf.RoundToInt(x + symbol.advance * fontScale) > regionWidth)
                {
                    if (x == 0f) return;

                    if (alignment != Alignment.Left && indexOffset < verts.size)
                    {
                        Align(verts, indexOffset, x - finalSpacingX);
                        indexOffset = verts.size;
                    }

                    v0x -= x;
                    v1x -= x;
                    v0y -= finalLineHeight;
                    v1y -= finalLineHeight;

                    x = 0;
                    y += finalLineHeight;
                    prevX = 0;
                }

                verts.Add(new Vector3(v0x, v0y));
                verts.Add(new Vector3(v0x, v1y));
                verts.Add(new Vector3(v1x, v1y));
                verts.Add(new Vector3(v1x, v0y));

                x += finalSpacingX + symbol.advance * fontScale;
                i += symbol.length - 1;
                prev = 0;

                if (uvs != null)
                {
                    Rect uv = symbol.uvRect;

                    float u0x = uv.xMin;
                    float u0y = uv.yMin;
                    float u1x = uv.xMax;
                    float u1y = uv.yMax;

                    uvs.Add(new Vector2(u0x, u0y));
                    uvs.Add(new Vector2(u0x, u1y));
                    uvs.Add(new Vector2(u1x, u1y));
                    uvs.Add(new Vector2(u1x, u0y));
                }

                if (cols != null)
                {
                    if (symbolStyle == SymbolStyle.Colored)
                    {
                        for (int b = 0; b < 4; ++b) cols.Add(uc);
                    }
                    else
                    {
                        Color32 col = Color.white;
                        col.a = uc.a;
                        for (int b = 0; b < 4; ++b) cols.Add(col);
                    }
                }
            }
            else // No symbol present
            {
                GlyphInfo glyph = GetGlyph(ch, prev);
                if (glyph == null) continue;
                prev = ch;

                if (subscriptMode != 0)
                {
                    glyph.v0.x *= sizeShrinkage;
                    glyph.v0.y *= sizeShrinkage;
                    glyph.v1.x *= sizeShrinkage;
                    glyph.v1.y *= sizeShrinkage;

                    if (subscriptMode == 1)
                    {
                        glyph.v0.y -= fontScale * fontSize * 0.4f;
                        glyph.v1.y -= fontScale * fontSize * 0.4f;
                    }
                    else
                    {
                        glyph.v0.y += fontScale * fontSize * 0.05f;
                        glyph.v1.y += fontScale * fontSize * 0.05f;
                    }
                }

                v0x = glyph.v0.x + x;
                v0y = glyph.v0.y - y;
                v1x = glyph.v1.x + x;
                v1y = glyph.v1.y - y;

                float w = glyph.advance;
                if (finalSpacingX < 0f) w += finalSpacingX;

                // Doesn't fit? Move down to the next line
                if (Mathf.RoundToInt(x + w) > regionWidth)
                {
                    if (x == 0f) return;

                    if (alignment != Alignment.Left && indexOffset < verts.size)
                    {
                        Align(verts, indexOffset, x - finalSpacingX);
                        indexOffset = verts.size;
                    }

                    v0x -= x;
                    v1x -= x;
                    v0y -= finalLineHeight;
                    v1y -= finalLineHeight;

                    x = 0;
                    y += finalLineHeight;
                    prevX = 0;
                }

                if (IsSpace(ch))
                {
                    if (underline)
                    {
                        ch = '_';
                    }
                    else if (strikethrough)
                    {
                        ch = '-';
                    }
                }

                // Advance the position
                x += (subscriptMode == 0) ? finalSpacingX + glyph.advance :
                    (finalSpacingX + glyph.advance) * sizeShrinkage;

                // No need to continue if this is a space character
                if (IsSpace(ch)) continue;

                // Texture coordinates
                if (uvs != null)
                {
                    if (bitmapFont != null)
                    {
                        glyph.u0.x = uvRect.xMin + invX * glyph.u0.x;
                        glyph.u2.x = uvRect.xMin + invX * glyph.u2.x;
                        glyph.u0.y = uvRect.yMax - invY * glyph.u0.y;
                        glyph.u2.y = uvRect.yMax - invY * glyph.u2.y;

                        glyph.u1.x = glyph.u0.x;
                        glyph.u1.y = glyph.u2.y;

                        glyph.u3.x = glyph.u2.x;
                        glyph.u3.y = glyph.u0.y;
                    }

                    for (int j = 0, jmax = (bold ? 4 : 1); j < jmax; ++j)
                    {
                        uvs.Add(glyph.u0);
                        uvs.Add(glyph.u1);
                        uvs.Add(glyph.u2);
                        uvs.Add(glyph.u3);
                    }
                }

                // Vertex colors
                if (cols != null)
                {
                    if (glyph.channel == 0 || glyph.channel == 15)
                    {
                        if (gradient)
                        {
                            float min = sizePD + glyph.v0.y / fontScale;
                            float max = sizePD + glyph.v1.y / fontScale;

                            min /= sizePD;
                            max /= sizePD;

                            s_c0 = Color.Lerp(gb, gt, min);
                            s_c1 = Color.Lerp(gb, gt, max);

                            for (int j = 0, jmax = (bold ? 4 : 1); j < jmax; ++j)
                            {
                                cols.Add(s_c0);
                                cols.Add(s_c1);
                                cols.Add(s_c1);
                                cols.Add(s_c0);
                            }
                        }
                        else
                        {
                            for (int j = 0, jmax = (bold ? 16 : 4); j < jmax; ++j)
                                cols.Add(uc);
                        }
                    }
                    else
                    {
                        // Packed fonts come as alpha masks in each of the RGBA channels.
                        // In order to use it we need to use a special shader.
                        //
                        // Limitations:
                        // - Effects (drop shadow, outline) will not work.
                        // - Should not be a part of the atlas (eastern fonts rarely are anyway).
                        // - Lower color precision

                        Color col = uc;

                        col *= 0.49f;

                        switch (glyph.channel)
                        {
                            case 1: col.b += 0.51f; break;
                            case 2: col.g += 0.51f; break;
                            case 4: col.r += 0.51f; break;
                            case 8: col.a += 0.51f; break;
                        }

                        Color32 c = col;
                        for (int j = 0, jmax = (bold ? 16 : 4); j < jmax; ++j)
                            cols.Add(c);
                    }
                }

                // Bold and italic contributed by Rudy Pangestu.
                if (!bold)
                {
                    if (!italic)
                    {
                        verts.Add(new Vector3(v0x, v0y));
                        verts.Add(new Vector3(v0x, v1y));
                        verts.Add(new Vector3(v1x, v1y));
                        verts.Add(new Vector3(v1x, v0y));
                    }
                    else // Italic
                    {
                        float slant = fontSize * 0.1f * ((v1y - v0y) / fontSize);
                        verts.Add(new Vector3(v0x - slant, v0y));
                        verts.Add(new Vector3(v0x + slant, v1y));
                        verts.Add(new Vector3(v1x + slant, v1y));
                        verts.Add(new Vector3(v1x - slant, v0y));
                    }
                }
                else // Bold
                {
                    for (int j = 0; j < 4; ++j)
                    {
                        float a = mBoldOffset[j * 2];
                        float b = mBoldOffset[j * 2 + 1];

                        float slant = (italic ? fontSize * 0.1f * ((v1y - v0y) / fontSize) : 0f);
                        verts.Add(new Vector3(v0x + a - slant, v0y + b));
                        verts.Add(new Vector3(v0x + a + slant, v1y + b));
                        verts.Add(new Vector3(v1x + a + slant, v1y + b));
                        verts.Add(new Vector3(v1x + a - slant, v0y + b));
                    }
                }

                // Underline and strike-through contributed by Rudy Pangestu.
                if (underline || strikethrough)
                {
                    GlyphInfo dash = GetGlyph(strikethrough ? '-' : '_', prev);
                    if (dash == null) continue;

                    if (uvs != null)
                    {
                        if (bitmapFont != null)
                        {
                            dash.u0.x = uvRect.xMin + invX * dash.u0.x;
                            dash.u2.x = uvRect.xMin + invX * dash.u2.x;
                            dash.u0.y = uvRect.yMax - invY * dash.u0.y;
                            dash.u2.y = uvRect.yMax - invY * dash.u2.y;
                        }

                        float cx = (dash.u0.x + dash.u2.x) * 0.5f;

                        for (int j = 0, jmax = (bold ? 4 : 1); j < jmax; ++j)
                        {
                            uvs.Add(new Vector2(cx, dash.u0.y));
                            uvs.Add(new Vector2(cx, dash.u2.y));
                            uvs.Add(new Vector2(cx, dash.u2.y));
                            uvs.Add(new Vector2(cx, dash.u0.y));
                        }
                    }

                    if (subscript && strikethrough)
                    {
                        v0y = (-y + dash.v0.y) * sizeShrinkage;
                        v1y = (-y + dash.v1.y) * sizeShrinkage;
                    }
                    else
                    {
                        v0y = (-y + dash.v0.y);
                        v1y = (-y + dash.v1.y);
                    }

                    if (bold)
                    {
                        for (int j = 0; j < 4; ++j)
                        {
                            float a = mBoldOffset[j * 2];
                            float b = mBoldOffset[j * 2 + 1];

                            verts.Add(new Vector3(prevX + a, v0y + b));
                            verts.Add(new Vector3(prevX + a, v1y + b));
                            verts.Add(new Vector3(x + a, v1y + b));
                            verts.Add(new Vector3(x + a, v0y + b));
                        }
                    }
                    else
                    {
                        verts.Add(new Vector3(prevX, v0y));
                        verts.Add(new Vector3(prevX, v1y));
                        verts.Add(new Vector3(x, v1y));
                        verts.Add(new Vector3(x, v0y));
                    }

                    if (gradient)
                    {
                        float min = sizePD + dash.v0.y / fontScale;
                        float max = sizePD + dash.v1.y / fontScale;

                        min /= sizePD;
                        max /= sizePD;

                        s_c0 = Color.Lerp(gb, gt, min);
                        s_c1 = Color.Lerp(gb, gt, max);

                        for (int j = 0, jmax = (bold ? 4 : 1); j < jmax; ++j)
                        {
                            cols.Add(s_c0);
                            cols.Add(s_c1);
                            cols.Add(s_c1);
                            cols.Add(s_c0);
                        }
                    }
                    else
                    {
                        for (int j = 0, jmax = (bold ? 16 : 4); j < jmax; ++j)
                            cols.Add(uc);
                    }
                }
            }
        }

        if (alignment != Alignment.Left && indexOffset < verts.size)
        {
            Align(verts, indexOffset, x - finalSpacingX);
            indexOffset = verts.size;
        }
        mColors.Clear();
    }

    static float[] mBoldOffset = new float[]
    {
        -0.25f, 0f, 0.25f, 0f,
        0f, -0.25f, 0f, 0.25f
    };

    /// <summary>
    /// Print character positions and indices into the specified buffer. Meant to be used with the "find closest vertex" calculations.
    /// </summary>

    static public void PrintApproximateCharacterPositions(string text, BetterList<Vector3> verts, BetterList<int> indices)
    {
        if (string.IsNullOrEmpty(text)) text = " ";

        Prepare(text);

        float x = 0f, y = 0f, maxX = 0f, halfSize = fontSize * fontScale * 0.5f;
        int textLength = text.Length, indexOffset = verts.size, ch = 0, prev = 0;

        for (int i = 0; i < textLength; ++i)
        {
            ch = text[i];

            verts.Add(new Vector3(x, -y - halfSize));
            indices.Add(i);

            if (ch == '\n')
            {
                if (x > maxX) maxX = x;

                if (alignment != Alignment.Left)
                {
                    Align(verts, indexOffset, x - finalSpacingX);
                    indexOffset = verts.size;
                }

                x = 0;
                y += finalLineHeight;
                prev = 0;
                continue;
            }
            else if (ch < ' ')
            {
                prev = 0;
                continue;
            }

            if (encoding && ParseSymbol(text, ref i))
            {
                --i;
                continue;
            }

            // See if there is a symbol matching this text
            BMSymbol symbol = useSymbols ? GetSymbol(text, i, textLength) : null;

            if (symbol == null)
            {
                float w = GetGlyphWidth(ch, prev);

                if (w != 0f)
                {
                    w += finalSpacingX;

                    if (Mathf.RoundToInt(x + w) > regionWidth)
                    {
                        if (x == 0f) return;

                        if (alignment != Alignment.Left && indexOffset < verts.size)
                        {
                            Align(verts, indexOffset, x - finalSpacingX);
                            indexOffset = verts.size;
                        }

                        x = w;
                        y += finalLineHeight;
                    }
                    else x += w;

                    verts.Add(new Vector3(x, -y - halfSize));
                    indices.Add(i + 1);
                    prev = ch;
                }
            }
            else
            {
                float w = symbol.advance * fontScale + finalSpacingX;

                if (Mathf.RoundToInt(x + w) > regionWidth)
                {
                    if (x == 0f) return;

                    if (alignment != Alignment.Left && indexOffset < verts.size)
                    {
                        Align(verts, indexOffset, x - finalSpacingX);
                        indexOffset = verts.size;
                    }

                    x = w;
                    y += finalLineHeight;
                }
                else x += w;

                verts.Add(new Vector3(x, -y - halfSize));
                indices.Add(i + 1);
                i += symbol.sequence.Length - 1;
                prev = 0;
            }
        }

        if (alignment != Alignment.Left && indexOffset < verts.size)
            Align(verts, indexOffset, x - finalSpacingX);
    }

    /// <summary>
    /// Print character positions and indices into the specified buffer.
    /// This function's data is meant to be used for precise character selection, such as clicking on a link.
    /// There are 2 vertices for every index: Bottom Left + Top Right.
    /// </summary>

    static public void PrintExactCharacterPositions(string text, BetterList<Vector3> verts, BetterList<int> indices)
    {
        if (string.IsNullOrEmpty(text)) text = " ";

        Prepare(text);

        float fullSize = fontSize * fontScale;
        float x = 0f, y = 0f, maxX = 0f;
        int textLength = text.Length, indexOffset = verts.size, ch = 0, prev = 0;

        for (int i = 0; i < textLength; ++i)
        {
            ch = text[i];

            if (ch == '\n')
            {
                if (x > maxX) maxX = x;

                if (alignment != Alignment.Left)
                {
                    Align(verts, indexOffset, x - finalSpacingX);
                    indexOffset = verts.size;
                }

                x = 0;
                y += finalLineHeight;
                prev = 0;
                continue;
            }
            else if (ch < ' ')
            {
                prev = 0;
                continue;
            }

            if (encoding && ParseSymbol(text, ref i))
            {
                --i;
                continue;
            }

            // See if there is a symbol matching this text
            BMSymbol symbol = useSymbols ? GetSymbol(text, i, textLength) : null;

            if (symbol == null)
            {
                float gw = GetGlyphWidth(ch, prev);

                if (gw != 0f)
                {
                    float w = gw + finalSpacingX;

                    if (Mathf.RoundToInt(x + w) > regionWidth)
                    {
                        if (x == 0f) return;

                        if (alignment != Alignment.Left && indexOffset < verts.size)
                        {
                            Align(verts, indexOffset, x - finalSpacingX);
                            indexOffset = verts.size;
                        }

                        x = 0f;
                        y += finalLineHeight;
                        prev = 0;
                        --i;
                        continue;
                    }

                    indices.Add(i);
                    verts.Add(new Vector3(x, -y - fullSize));
                    verts.Add(new Vector3(x + w, -y));
                    prev = ch;
                    x += w;
                }
            }
            else
            {
                float w = symbol.advance * fontScale + finalSpacingX;

                if (Mathf.RoundToInt(x + w) > regionWidth)
                {
                    if (x == 0f) return;

                    if (alignment != Alignment.Left && indexOffset < verts.size)
                    {
                        Align(verts, indexOffset, x - finalSpacingX);
                        indexOffset = verts.size;
                    }

                    x = 0f;
                    y += finalLineHeight;
                    prev = 0;
                    --i;
                    continue;
                }

                indices.Add(i);
                verts.Add(new Vector3(x, -y - fullSize));
                verts.Add(new Vector3(x + w, -y));
                i += symbol.sequence.Length - 1;
                x += w;
                prev = 0;
            }
        }

        if (alignment != Alignment.Left && indexOffset < verts.size)
            Align(verts, indexOffset, x - finalSpacingX);
    }

    /// <summary>
    /// Print the caret and selection vertices. Note that it's expected that 'text' has been stripped clean of symbols.
    /// </summary>

    static public void PrintCaretAndSelection(string text, int start, int end, BetterList<Vector3> caret, BetterList<Vector3> highlight)
    {
        if (string.IsNullOrEmpty(text)) text = " ";

        Prepare(text);

        int caretPos = end;

        if (start > end)
        {
            end = start;
            start = caretPos;
        }

        float x = 0f, y = 0f, maxX = 0f, fs = fontSize * fontScale;
        int caretOffset = (caret != null) ? caret.size : 0;
        int highlightOffset = (highlight != null) ? highlight.size : 0;
        int textLength = text.Length, index = 0, ch = 0, prev = 0;
        bool highlighting = false, caretSet = false;

        Vector2 last0 = Vector2.zero;
        Vector2 last1 = Vector2.zero;

        for (; index < textLength; ++index)
        {
            // Print the caret
            if (caret != null && !caretSet && caretPos <= index)
            {
                caretSet = true;
                caret.Add(new Vector3(x - 1f, -y - fs));
                caret.Add(new Vector3(x - 1f, -y));
                caret.Add(new Vector3(x + 1f, -y));
                caret.Add(new Vector3(x + 1f, -y - fs));
            }

            ch = text[index];

            if (ch == '\n')
            {
                // Used for alignment purposes
                if (x > maxX) maxX = x;

                // Align the caret
                if (caret != null && caretSet)
                {
                    if (alignment != Alignment.Left) Align(caret, caretOffset, x - finalSpacingX);
                    caret = null;
                }

                if (highlight != null)
                {
                    if (highlighting)
                    {
                        // Close the selection on this line
                        highlighting = false;
                        highlight.Add(last1);
                        highlight.Add(last0);
                    }
                    else if (start <= index && end > index)
                    {
                        // This must be an empty line. Add a narrow vertical highlight.
                        highlight.Add(new Vector3(x, -y - fs));
                        highlight.Add(new Vector3(x, -y));
                        highlight.Add(new Vector3(x + 2f, -y));
                        highlight.Add(new Vector3(x + 2f, -y - fs));
                    }

                    // Align the highlight
                    if (alignment != Alignment.Left && highlightOffset < highlight.size)
                    {
                        Align(highlight, highlightOffset, x - finalSpacingX);
                        highlightOffset = highlight.size;
                    }
                }

                x = 0;
                y += finalLineHeight;
                prev = 0;
                continue;
            }
            else if (ch < ' ')
            {
                prev = 0;
                continue;
            }

            if (encoding && ParseSymbol(text, ref index))
            {
                --index;
                continue;
            }

            // See if there is a symbol matching this text
            BMSymbol symbol = useSymbols ? GetSymbol(text, index, textLength) : null;
            float w = (symbol != null) ? symbol.advance * fontScale : GetGlyphWidth(ch, prev);

            if (w != 0f)
            {
                float v0x = x;
                float v1x = x + w;
                float v0y = -y - fs;
                float v1y = -y;

                if (Mathf.RoundToInt(v1x + finalSpacingX) > regionWidth)
                {
                    if (x == 0f) return;

                    // Used for alignment purposes
                    if (x > maxX) maxX = x;

                    // Align the caret
                    if (caret != null && caretSet)
                    {
                        if (alignment != Alignment.Left) Align(caret, caretOffset, x - finalSpacingX);
                        caret = null;
                    }

                    if (highlight != null)
                    {
                        if (highlighting)
                        {
                            // Close the selection on this line
                            highlighting = false;
                            highlight.Add(last1);
                            highlight.Add(last0);
                        }
                        else if (start <= index && end > index)
                        {
                            // This must be an empty line. Add a narrow vertical highlight.
                            highlight.Add(new Vector3(x, -y - fs));
                            highlight.Add(new Vector3(x, -y));
                            highlight.Add(new Vector3(x + 2f, -y));
                            highlight.Add(new Vector3(x + 2f, -y - fs));
                        }

                        // Align the highlight
                        if (alignment != Alignment.Left && highlightOffset < highlight.size)
                        {
                            Align(highlight, highlightOffset, x - finalSpacingX);
                            highlightOffset = highlight.size;
                        }
                    }

                    v0x -= x;
                    v1x -= x;
                    v0y -= finalLineHeight;
                    v1y -= finalLineHeight;

                    x = 0;
                    y += finalLineHeight;
                }

                x += w + finalSpacingX;

                // Print the highlight
                if (highlight != null)
                {
                    if (start > index || end <= index)
                    {
                        if (highlighting)
                        {
                            // Finish the highlight
                            highlighting = false;
                            highlight.Add(last1);
                            highlight.Add(last0);
                        }
                    }
                    else if (!highlighting)
                    {
                        // Start the highlight
                        highlighting = true;
                        highlight.Add(new Vector3(v0x, v0y));
                        highlight.Add(new Vector3(v0x, v1y));
                    }
                }

                // Save what the character ended with
                last0 = new Vector2(v1x, v0y);
                last1 = new Vector2(v1x, v1y);
                prev = ch;
            }
        }

        // Ensure we always have a caret
        if (caret != null)
        {
            if (!caretSet)
            {
                caret.Add(new Vector3(x - 1f, -y - fs));
                caret.Add(new Vector3(x - 1f, -y));
                caret.Add(new Vector3(x + 1f, -y));
                caret.Add(new Vector3(x + 1f, -y - fs));
            }

            if (alignment != Alignment.Left)
                Align(caret, caretOffset, x - finalSpacingX);
        }

        // Close the selection
        if (highlight != null)
        {
            if (highlighting)
            {
                // Finish the highlight
                highlight.Add(last1);
                highlight.Add(last0);
            }
            else if (start < index && end == index)
            {
                // Happens when highlight ends on an empty line. Highlight it with a thin line.
                highlight.Add(new Vector3(x, -y - fs));
                highlight.Add(new Vector3(x, -y));
                highlight.Add(new Vector3(x + 2f, -y));
                highlight.Add(new Vector3(x + 2f, -y - fs));
            }

            // Align the highlight
            if (alignment != Alignment.Left && highlightOffset < highlight.size)
                Align(highlight, highlightOffset, x - finalSpacingX);
        }
    }
}